﻿//---------------------------------------------------------------------------------
// Copyright 2011 Microsoft Corporation
// Licensed under the Microsoft Public License (MS-PL) (the "License"); 
// You may not use this file except in compliance with the License. 
// You may obtain a copy of the License at http://www.opensource.org/licenses/ms-pl.html
// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR 
// CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, 
// INCLUDING WITHOUT LIMITATION ANY IMPLIED WARRANTIES OR 
// CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, 
// MERCHANTABLITY OR NON-INFRINGEMENT. 
// See the Microsoft Public License (MS-PL) for the specific language governing 
// permissions and limitations under the License.
//---------------------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.Data.Services.Client;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Net;
using System.Reflection;
using System.Threading;
using System.Xml;
using System.Xml.Linq;
using Newtonsoft.Json.Linq;

namespace Microsoft.Telco.WindowsLive
{
	public class LiveDataContext : DataServiceContext
	{

		private int connecting;
		private AppInformation appInfo;
		private bool isFullTrustAuth = false;
		private bool isSignedIn;
		public SignedInUser SignedInUser { get; set; }
		private DataServiceQuery<Contact> _Contacts;
		private Dictionary<string, Uri> baseUris;
		private Dictionary<WebRequestData, object> outstandingWebRequests;
		private static AsyncCallback readServiceDocumentCompleted;
		private Uri serviceRoot;

		private string DelAuthToken
		{
			get
			{
				if (!this.isFullTrustAuth)
				{
					return ("WRAP access_token=" + this.appInfo.AuthInfo.AccessToken);
				}
				return this.appInfo.AuthInfo.AccessToken;
			}
		}

		public LiveDataContext()
			: base(new Uri("http://apis.live.net"))
		{
			this.baseUris = new Dictionary<string, Uri>();
			this.outstandingWebRequests = new Dictionary<WebRequestData, object>();
			this.OnContextCreated();
		}

		static LiveDataContext()
		{
			readServiceDocumentCompleted = new AsyncCallback(LiveDataContext.ReadServiceDocumentCompleted);
		}

		private static void ReadServiceDocumentCompleted(IAsyncResult asyncResult)
		{
			if (asyncResult.IsCompleted)
			{
				WebRequestData asyncState = asyncResult.AsyncState as WebRequestData;
				LiveDataContext dataContext = asyncState.DataContext;
				Exception exception = null;
				WebRequest webRequest = asyncState.WebRequest;
				WebResponse webResponse = null;
				bool flag = false;
				try
				{
					webResponse = webRequest.EndGetResponse(asyncResult);
					if (webResponse == null)
					{
						throw new Exception(string.Format(CultureInfo.CurrentCulture, StringResource.NullResponseReceived, new object[] { webRequest.RequestUri.ToString() }));
					}
					ParseReturnedServiceDocumentXml(dataContext, webResponse, true, asyncState.Callback);
				}
				catch (Exception exception2)
				{
					if (asyncState.IsRootDocument)
					{
						exception = exception2;
						flag = true;
					}
				}
				lock (dataContext.outstandingWebRequests)
				{
					dataContext.outstandingWebRequests.Remove(asyncState);
					if (dataContext.outstandingWebRequests.Count == 0)
					{
						flag = true;
					}
				}
				if (flag)
				{
					dataContext.isSignedIn = exception == null;
					if (asyncState.Callback != null)
					{
						asyncState.Callback(exception);
					}
				}
			}
		}

		private static void ParseReturnedServiceDocumentXml(LiveDataContext dataContext, WebResponse webResponse, bool isAsync, Action<Exception> asyncCallbak)
		{
			XmlReader reader = XmlReader.Create(webResponse.GetResponseStream());
			reader.Read();
			XElement element = XElement.Load(reader);
			Uri uri = null;
			XAttribute attribute = element.Attribute(XName.Get("base", "http://www.w3.org/XML/1998/namespace"));
			if (attribute != null)
			{
				uri = new Uri(attribute.Value, UriKind.Absolute);
			}
			IEnumerable<XElement> source = element.Elements(XName.Get("workspace", "http://www.w3.org/2007/app"));
			XElement element2 = null;
			if (source.Count<XElement>() > 1)
			{
				foreach (XElement element3 in source)
				{
					if (string.Compare(element3.Elements(XName.Get("title", "http://www.w3.org/2005/Atom")).First<XElement>().Value, "v4.0", StringComparison.InvariantCultureIgnoreCase) == 0)
					{
						element2 = element3;
						break;
					}
				}
			}
			else
			{
				element2 = source.FirstOrDefault<XElement>();
			}
			if ((dataContext.SignedInUser == null) && (element2 != null))
			{
				XElement userElement = element2.Elements(XName.Get("SignedInUser", "http://schemas.microsoft.com/ado/2007/08/dataservices")).Select<XElement, XElement>(delegate(XElement user)
				{
					return user;
				}).SingleOrDefault<XElement>();
				if (userElement != null)
				{
					dataContext.SignedInUser = new SignedInUser();
					PopulateSignedInUser(userElement, dataContext.SignedInUser);
				}
			}
			if (element2 != null)
			{
				if (!string.IsNullOrEmpty(element2.Attribute(XName.Get("base", "http://www.w3.org/XML/1998/namespace")).Value))
				{
					uri = new Uri(element2.Attribute(XName.Get("base", "http://www.w3.org/XML/1998/namespace")).Value, UriKind.Absolute);
				}
				string text1 = element2.Elements(XName.Get("title", "http://www.w3.org/2005/Atom")).First<XElement>().Value;
				foreach (XElement element5 in element2.Elements(XName.Get("collection", "http://www.w3.org/2007/app")))
				{
					IEnumerable<XElement> enumerable2 = element5.Descendants(XName.Get("workspace", "http://www.w3.org/2007/app"));
					if (enumerable2.Count<XElement>() > 0)
					{
						XElement element6 = enumerable2.FirstOrDefault<XElement>();
						Uri baseUri = uri;
						if (!string.IsNullOrEmpty(element6.Attribute(XName.Get("base", "http://www.w3.org/XML/1998/namespace")).Value))
						{
							baseUri = new Uri(element6.Attribute(XName.Get("base", "http://www.w3.org/XML/1998/namespace")).Value, UriKind.Absolute);
						}
						foreach (XElement element7 in element6.Elements(XName.Get("collection", "http://www.w3.org/2007/app")))
						{
							string text2 = element7.Elements(XName.Get("title", "http://www.w3.org/2005/Atom")).First<XElement>().Value;
							Uri uri3 = new Uri(baseUri, element7.Attribute(XName.Get("href")).Value);
							string key = element7.Element(XName.Get("title", "http://www.w3.org/2005/Atom")).Value;
							lock (dataContext.baseUris)
							{
								if (!dataContext.baseUris.ContainsKey(key))
								{
									dataContext.baseUris.Add(key, uri3);
								}
								continue;
							}
						}
						continue;
					}
				}
			}
		}

		private static void PopulateSignedInUser(XElement userElement, SignedInUser user)
		{
			foreach (XElement element in userElement.Elements())
			{
				string localName = element.Name.LocalName;
				if (localName != null)
				{
					if (!(localName == "Cid"))
					{
						if (localName == "Uri")
						{
							goto Label_004E;
						}
					}
					else
					{
						user.Cid = element.Value;
					}
				}
				continue;
			Label_004E:
				user.Uri = element.Value;
			}
		}

		private void OnContextCreated()
		{
			this.serviceRoot = base.BaseUri;
			base.ResolveName = new Func<Type, string>(this.ResolveNameFromType);
			base.ResolveType = new Func<string, Type>(this.ResolveTypeFromName);
			base.ResolveSet = new Func<string, Uri>(this.ResolveEntitySet);
			base.SendingRequest += new EventHandler<SendingRequestEventArgs>(this.OnBeforeSendingRequest);
			base.ReadingEntity += new EventHandler<ReadingWritingEntityEventArgs>(this.OnReadingEntity);
			base.SaveChangesDefaultOptions = SaveChangesOptions.ReplaceOnUpdate;
			base.MergeOption = MergeOption.OverwriteChanges;
			base.IgnoreMissingProperties = true;
		}

		private void OnReadingEntity(object sender, ReadingWritingEntityEventArgs e)
		{
			if ((e != null) && (e.Entity is LiveResource))
			{
				((LiveResource)e.Entity).SetDataContext(this);
			}
		}

		protected string ResolveNameFromType(Type clientType)
		{
			if (clientType.Namespace.Equals("Microsoft.Live", StringComparison.Ordinal))
			{
				return clientType.Name;
			}
			return clientType.FullName;
		}

		protected Type ResolveTypeFromName(string typeName)
		{
			if (typeName.LastIndexOf('.') == -1)
			{
                return base.GetType().Assembly.GetType("Microsoft.Telco.WindowsLive." + typeName, true);
				//return base.GetType().Assembly.GetType("Microsoft.Live." + typeName, true);
			}
			return null;
		}

		internal Uri ResolveEntitySet(string entitySetName)
		{
			if (!this.isSignedIn)
			{
				throw new InvalidOperationException(StringResource.UserNotSignedIn);
			}
			Uri uri = null;
			if (Uri.IsWellFormedUriString(entitySetName, UriKind.Absolute))
			{
				uri = new Uri(entitySetName);
			}
			if (this.baseUris.ContainsKey(entitySetName))
			{
				uri = this.baseUris[entitySetName];
			}
			if (uri == null)
			{
				throw new DataServiceClientException(string.Format(CultureInfo.CurrentCulture, StringResource.CantFindBaseUri, new object[] { entitySetName }));
			}
			return uri;
		}

		private void OnBeforeSendingRequest(object sender, SendingRequestEventArgs e)
		{
			if (!this.isSignedIn)
			{
				throw new InvalidOperationException(StringResource.UserNotSignedIn);
			}
			e.RequestHeaders["Authorization"] = this.DelAuthToken;
			e.RequestHeaders["X-HTTP-Live-ApplicationId"] = this.appInfo.ClientId;
			SetHeaderForAnalytics(e.RequestHeaders);
		}

		private static void SetHeaderForAnalytics(WebHeaderCollection headers)
		{
			AssemblyName name = typeof(LiveDataContext).Assembly.GetName();
			headers["X-HTTP-Live-Library"] = string.Format(CultureInfo.InvariantCulture, "{0}.{1}/{2}", new object[] { name.Name, "Net", name.Version.ToString() });
		}

		public LiveDataServiceCollection<Contact> GetContactsCollection()
		{
			return new LiveDataServiceCollection<Contact>(this, this.ContactsQuery);
		}

		public DataServiceQuery<Contact> ContactsQuery
		{
			get
			{
				if (this._Contacts == null)
				{
					this._Contacts = base.CreateQuery<Contact>("AllContacts");
				}
				return this._Contacts;
			}
		}

		private bool InitializeSignInContext(AppInformation appInfo)
		{
			this.appInfo = appInfo;
			this.serviceRoot = appInfo.ServiceRoot;
			//this.uiSyncContext = SynchronizationContext.Current;
			return ((appInfo.AuthInfo != null) && (!string.IsNullOrEmpty(appInfo.AuthInfo.AccessToken) || !string.IsNullOrEmpty(appInfo.AuthInfo.RefreshToken)));
		}

		private void CheckSignInStatus()
		{
			if (Interlocked.CompareExchange(ref this.connecting, 1, 0) == 1)
			{
				throw new InvalidOperationException(StringResource.SignInInProgress);
			}
			if (this.isSignedIn)
			{
				throw new InvalidOperationException(StringResource.UserAlreadySignedIn);
			}
		}

		private static AuthorizationException CreateAuthorizationException(string error, Exception e)
		{
			if ((e != null) && (e is AuthorizationException))
			{
				return (e as AuthorizationException);
			}
			return new AuthorizationException(error, e);
		}

		private void GetBaseUrisFromServiceDocument(Uri serviceDocUri, bool isRootDoc)
		{
			if (string.Compare(serviceDocUri.AbsolutePath, "/") == 0)
			{
				serviceDocUri = new Uri(serviceDocUri.ToString() + "v4.1");
			}
			Uri requestUri = new Uri(serviceDocUri.ToString() + "?$expand=Contacts,Profiles,Activities,Documents,Photos,Calendar,Sync");
			WebRequest request = WebRequest.Create(requestUri);
			request.Method = "GET";
			request.Headers["Authorization"] = this.DelAuthToken;
			request.Headers["X-HTTP-Live-ApplicationId"] = this.appInfo.ClientId;
			SetHeaderForAnalytics(request.Headers);
			WebResponse webResponse = null;
			try
			{
				webResponse = request.GetResponse();
				if (webResponse == null)
				{
					throw new Exception(string.Format(CultureInfo.CurrentCulture, StringResource.NullResponseReceived, new object[] { serviceDocUri.ToString() }));
				}
			}
			catch (WebException)
			{
				if (isRootDoc)
				{
					throw;
				}
				return;
			}
			ParseReturnedServiceDocumentXml(this, webResponse, false, null);
		}

		private void ProcessServiceDocuments()
		{
			if (this.baseUris.Count > 0)
			{
				this.isSignedIn = true;
			}
			else
			{
				try
				{
					this.GetBaseUrisFromServiceDocument(this.serviceRoot, true);
					this.isSignedIn = true;
				}
				catch (Exception exception)
				{
					this.appInfo.AuthInfo.Error = CreateAuthorizationException(StringResource.FailToReadServiceDoc, exception);
					throw exception;
				}
			}
		}

		private static bool IsUnauthorizedException(Exception e)
		{
			bool flag = false;
			WebException exception = e as WebException;
			if ((exception != null) && (exception.Response != null))
			{
				HttpWebResponse response = exception.Response as HttpWebResponse;
				if ((response != null) && (response.StatusCode == HttpStatusCode.Unauthorized))
				{
					flag = true;
				}
			}
			return flag;
		}

		private bool IsWindowsApp
		{
			get
			{
				return string.IsNullOrEmpty(this.appInfo.CallbackUrl);
			}
		}

		public void SignIn(AppInformation applicationInfo)
		{
			if (applicationInfo == null)
			{
				throw new ArgumentNullException("applicationInfo");
			}
			bool flag = this.InitializeSignInContext(applicationInfo);
			try
			{
				this.CheckSignInStatus();
				bool flag2 = !flag;
				if (!flag)
				{
					goto Label_015E;
				}
				if (!string.IsNullOrEmpty(this.appInfo.AuthInfo.AccessToken))
				{
					try
					{
						this.ProcessServiceDocuments();
						return;
					}
					catch (Exception exception)
					{
						if (!IsUnauthorizedException(exception) || string.IsNullOrEmpty(this.appInfo.AuthInfo.RefreshToken))
						{
							this.appInfo.AuthInfo.Error = CreateAuthorizationException(StringResource.FailToReadServiceDoc, exception);
							throw this.appInfo.AuthInfo.Error;
						}
					}
				}
				if (!this.IsWindowsApp)
				{
					this.appInfo.AuthInfo.Error = CreateAuthorizationException(StringResource.FailToRefresh, this.appInfo.AuthInfo.Error);
					throw this.appInfo.AuthInfo.Error;
				}
				flag2 = true;
			Label_015E:
				if (flag2)
				{
					if (string.IsNullOrEmpty(this.appInfo.ClientId))
					{
						throw new ArgumentException(StringResource.NoClientId, "applicationInfo.ClientId");
					}
					if (string.IsNullOrEmpty(this.appInfo.ClientSecret))
					{
						throw new ArgumentException(StringResource.NoClientSecret, "applicationInfo.ClientSecret");
					}
					if (this.appInfo.RequestedOffers.Count == 0)
					{
						throw new ArgumentException(StringResource.NoOffer, "applicationInfo.RequestedOffers");
					}
					try
					{
						//this.LaunchLoginDialog();
						if (this.appInfo.AuthInfo.Error != null)
						{
							throw this.appInfo.AuthInfo.Error;
						}
						this.ProcessServiceDocuments();
					}
					catch (Exception exception3)
					{
						if (!IsUnauthorizedException(exception3) && (this.appInfo.AuthInfo.Error == null))
						{
							this.appInfo.AuthInfo.Error = CreateAuthorizationException(StringResource.FailToAuthenticate, null);
						}
						throw this.appInfo.AuthInfo.Error;
					}
				}
			}
			finally
			{
				this.OnSignInCompleted(false);
			}
		}

		private void OnSignInCompleted(bool completeRequest)
		{
			Interlocked.Exchange(ref this.connecting, 0);
			if (completeRequest)
			{
				this.HandleCompletion(this.isSignedIn);
			}
		}

		private void HandleCompletion(bool success)
		{
            // No-op
		}

        public Presence GetPresence(string cid)
        {
           Stream responseStream= MakePresenceRequest(GetPresenceUri(cid)); 
           return ParseJsonResponse(responseStream);
        }

        private Presence ParseJsonResponse(Stream responseStream)
        {
            Presence presence = new Presence();
            try
            {

                StreamReader reader = new StreamReader(responseStream);
                JObject jObject = JObject.Parse(reader.ReadToEnd());
                presence.StatusIcon = new Icon { Url = (string)jObject.SelectToken("icon.url"), Height = (string)jObject.SelectToken("icon.height").ToString(), Width = (string)jObject.SelectToken("icon.width").ToString()};
                if ((string)jObject.SelectToken("status") != null)
                {
                    presence.PresenceStatus = (Status)Enum.Parse(typeof(Status), (string)jObject.SelectToken("status"));
                }
                else
                {
                    presence.PresenceStatus = Status.Unknown;
                }
                presence.StatusText = (string)jObject.SelectToken("statusText");
                presence.Id = (string)jObject.SelectToken("id");
                presence.DisplayName = (string)jObject.SelectToken("displayName");
                presence.RequestStatus = new Result { Code = (string)jObject.SelectToken("result.code").ToString(), Response = (string)jObject.SelectToken("result.response") };

            }
            catch
            {
                presence = null;

            }
            return presence;
        }

        private Stream MakePresenceRequest(Uri requestUri)
        {
            Stream responseStream=null;
            try
            {
                lock (this)
                {
                    WebRequest presenceRequest = WebRequest.Create(requestUri);
                    WebResponse presenceResponse = presenceRequest.GetResponse();
                    if (presenceResponse != null)
                    {
                        HttpWebResponse presenceWebResponse = (HttpWebResponse)presenceResponse;
                        if (presenceWebResponse.StatusCode == HttpStatusCode.OK)
                        {
                            responseStream = presenceResponse.GetResponseStream();
                        }
                        else
                        {
                            throw new Exception("Presence Request failed");
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                throw ex;
            }
            return responseStream;
        }

        private Uri GetPresenceUri(string cid)
        {
            return new Uri(string.Format("http://messenger.services.live.com/users/{0}@apps.messenger.live.com/presence/", cid));
        }
	}

}